/**
 * Example 3: Different Timeouts in Event Handling
 *
 * This example demonstrates the problems that occur when different modules
 * use the same named event but with different wait timeouts. This adds complexity
 * to the event handling behavior, especially with auto-reset events.
 */

#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <sstream>

// Get current time as string for logging
std::string GetTimeString() {
    SYSTEMTIME st;
    GetLocalTime(&st);
    
    std::ostringstream oss;
    oss << std::setfill('0') 
        << std::setw(2) << st.wHour << ":" 
        << std::setw(2) << st.wMinute << ":" 
        << std::setw(2) << st.wSecond << "." 
        << std::setw(3) << st.wMilliseconds;
    
    return oss.str();
}

// Log with timestamp
void LogWithTime(const std::string& message) {
    std::cout << "[" << GetTimeString() << "] " << message << std::endl;
}

// Create a named event
HANDLE CreateNamedEvent(bool manualReset) {
    LogWithTime(manualReset ? "Creating manual-reset event..." : "Creating auto-reset event...");
    
    HANDLE hEvent = CreateEvent(
        NULL,             // Default security attributes
        manualReset,      // Reset mode
        FALSE,            // Initial state is nonsignaled
        L"Global\\Example3_Event" // Named event
    );
    
    if (hEvent == NULL) {
        std::cerr << "CreateEvent failed with error " << GetLastError() << std::endl;
        return NULL;
    }
    
    DWORD lastError = GetLastError();
    if (lastError == ERROR_ALREADY_EXISTS) {
        LogWithTime("Event already exists! Got handle to existing event.");
    }
    
    return hEvent;
}

// Thread function for module with specific timeout
DWORD WINAPI ModuleThreadFunc(LPVOID lpParam) {
    struct ThreadParams {
        HANDLE hEvent;
        DWORD timeout;
        std::string moduleName;
    };
    
    ThreadParams* params = static_cast<ThreadParams*>(lpParam);
    HANDLE hEvent = params->hEvent;
    DWORD timeout = params->timeout;
    std::string moduleName = params->moduleName;
    
    for (int i = 0; i < 3; i++) {  // Each thread will attempt to catch 3 events
        LogWithTime(moduleName + ": Waiting for event with timeout " + std::to_string(timeout) + "ms");
        
        DWORD waitResult = WaitForSingleObject(hEvent, timeout);
        
        if (waitResult == WAIT_OBJECT_0) {
            LogWithTime(moduleName + ": Event signaled! Processing notification");
            
            // Simulate processing time - varies by module
            Sleep(100);  // Small processing delay for demo purposes
            
            LogWithTime(moduleName + ": Processing complete");
        }
        else if (waitResult == WAIT_TIMEOUT) {
            LogWithTime(moduleName + ": Wait timed out!");
        }
        else {
            LogWithTime(moduleName + ": Wait failed with error " + std::to_string(GetLastError()));
            break;
        }
    }
    
    LogWithTime(moduleName + ": Thread completed operation");
    delete params;  // Clean up the parameters
    return 0;
}

int main() {
    std::cout << "Example 3: Different Timeouts in Event Handling\n" << std::endl;
    
    // Create an event - for this example, we'll use auto-reset to show the timeout issues clearly
    bool useManualReset = false;  // Auto-reset event
    HANDLE hEvent = CreateNamedEvent(useManualReset);
    if (hEvent == NULL) {
        return 1;
    }
    
    // Create multiple threads with different timeouts
    std::vector<HANDLE> threads;
    
    // Define modules with different timeouts
    struct ModuleConfig {
        std::string name;
        DWORD timeout;
    };
    
    ModuleConfig modules[] = {
        {"Module A", INFINITE},  // No timeout (infinite wait)
        {"Module B", 5000},     // 5 second timeout
        {"Module C", 1000}      // 1 second timeout
    };
    
    // Start threads for each module
    for (int i = 0; i < 3; i++) {
        auto* params = new struct {
            HANDLE hEvent;
            DWORD timeout;
            std::string moduleName;
        };
        
        params->hEvent = hEvent;
        params->timeout = modules[i].timeout;
        params->moduleName = modules[i].name;
        
        HANDLE hThread = CreateThread(NULL, 0, ModuleThreadFunc, params, 0, NULL);
        if (hThread == NULL) {
            std::cerr << "CreateThread failed with error " << GetLastError() << std::endl;
            CloseHandle(hEvent);
            return 1;
        }
        
        threads.push_back(hThread);
    }
    
    // Wait for threads to initialize
    Sleep(1000);
    
    // Scenario demonstration
    LogWithTime("\n--- Scenario 1: Normal Event Signal ---");
    LogWithTime("Main: Signaling the event. Only one thread will receive it (auto-reset).");
    SetEvent(hEvent);
    Sleep(1000);  // Give time for a thread to process
    
    LogWithTime("\n--- Scenario 2: Auto-Reset and Race Condition ---");
    LogWithTime("Main: Signaling the event again. Another thread will receive it.");
    SetEvent(hEvent);
    Sleep(1000);  // Give time for a thread to process
    
    LogWithTime("\n--- Scenario 3: Timeout Effects ---");
    LogWithTime("Main: Waiting 2 seconds without signaling the event.");
    LogWithTime("     Module C (1s timeout) should time out, others continue waiting.");
    Sleep(2000);
    
    LogWithTime("Main: Signaling the event again.");
    SetEvent(hEvent);
    Sleep(1000);  // Give time for a thread to process
    
    LogWithTime("\n--- Scenario 4: More Timeouts ---");
    LogWithTime("Main: Waiting 6 seconds without signaling the event.");
    LogWithTime("     Module C (1s timeout) and Module B (5s timeout) should time out.");
    Sleep(6000);
    
    // Final event signal to potentially wake up any remaining threads
    LogWithTime("Main: Signaling the event one last time.");
    SetEvent(hEvent);
    
    // Wait for all threads to finish (give them time to complete)
    LogWithTime("Main: Waiting for all threads to complete...");
    WaitForMultipleObjects(static_cast<DWORD>(threads.size()), &threads[0], TRUE, 10000);
    
    // Summary
    LogWithTime("\nMain: Demo completed. Observations:");
    LogWithTime("  1. Only one thread receives each event signal with auto-reset events");
    LogWithTime("  2. Which thread gets the signal depends on thread timing");
    LogWithTime("  3. Threads with timeouts may miss signals if they time out");
    LogWithTime("  4. This creates unpredictable behavior in multi-module systems");
    
    // Clean up
    for (size_t i = 0; i < threads.size(); i++) {
        CloseHandle(threads[i]);
    }
    CloseHandle(hEvent);
    
    std::cout << "\nPress Enter to exit..." << std::endl;
    std::cin.get();
    
    return 0;
}
